﻿//-----------------------------------------------------------------------
// <copyright file="SetAttribute.cs" company="Microsoft">
// Copyright (c) Microsoft Corporation.  All rights reserved.
// THIS CODE AND INFORMATION ARE PROVIDED "AS IS" WITHOUT WARRANTY OF ANY 
// KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
// IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A
// PARTICULAR PURPOSE.
// </copyright>
// <author>Steve Overall</author>
// <email>v-stevov</email>
// <date>2005-04-15</date>
// <summary>Writes the specified attribute to any elements matching the sepcified XPath.</summary>
//-----------------------------------------------------------------------

namespace Microsoft.Sdc.Tasks.XmlFile
{
	#region Using directives

	using System;
	using System.Collections.Generic;
	using System.Text;
	using System.Xml;

	using Microsoft.Build.Framework;
	using Microsoft.Build.Tasks;
	using Microsoft.Build.Utilities;

	using Microsoft.Sdc.Tasks;

	#endregion

	#region class comments
	/// <summary>
	/// Writes an attribute to every element that matches the specified XPath expression.
	/// Will fail if no matches are found for the XPath expression.
	/// Unlike SetValue, if the element is found, but the attribute does not already exist 
	/// the task will not fail.  Instead the attribute will be added.
	/// </summary>
	/// <remarks>
	/// <code>
	/// <![CDATA[<XmlFile.SetAttribute Path="path" XPath="xpath" Name="name" Value="value" Namespaces="namespaces" IgnoreNoMatchFailure="ignoreNoMatchFailure" Force="force"/>]]>
	/// </code>
	/// <para>where:</para>
	/// <para><i>Path (Required)</i></para>
	/// <para>The path of the XML file to read.</para>
	/// <para><i>XPath (Required)</i></para>
	/// <para>The XPath expression to evaluate for the element(s) on which to set the attribute.</para>
	///	<para><i>Name</i></para>
	/// <para>The name of the attribute to set.</para>
	/// <para><i>Value</i></para>
	/// <para>The value to assign to the attribute.</para>
	/// <para><i>Namespaces</i></para>
	/// <para>
	/// Optional TaskItems array detailing each of the namespace declarations required to read/maintain the target document.
	/// Each TaskItem should have 2 elements &lt;Prefix&gt; and &lt;Uri&gt;.
	/// </para>
	/// <para><i>IgnoreNoMatchFailure</i></para>
	/// <para>If set to true, no Exception will be thrown if no matches made for the supplied XPath.  Defaults to false.</para>
	/// <para><i>Force</i></para>
	/// <para>If set to true, this will force the file to be updated even if it is Read Only.  Defaults to false.</para>
	/// </remarks>
	/// <example>
	/// <code><![CDATA[
	/// <Project>
	///     <Target Name="Test" >
	///         <XmlFile.SetAttribute 
	///					Path="c:\app.config"
	///					XPath="/configuration/appSettings/add[@key='myKey']"
	///					Name="value"
	///					Value="my data value"
	///					Namespaces="@(ConfigNamespaces)"
	///					IgnoreNoMatchFailure="true"
	///					Force="true"
	///			/>
	///     </Target>
	/// </Project>
	/// ]]></code>    
	/// </example>
	#endregion	
	public class SetAttribute : BaseXmlFileTask
	{
		#region private declarations
		private string			_name					= String.Empty;
		private string			_value					= String.Empty;
		private bool			_ignoreNoMatchFailure	= false;
		private bool			_force					= false;
		#endregion

		#region constructors
		/// <summary>
		/// Create a new ChangeValue.
		/// </summary>
		public SetAttribute() : base()
		{
		}
		#endregion

		#region override of InternalExecute
		/// <summary>
		/// Carry out this task's action.
		/// </summary>
		protected override void InternalExecute()
		{
			//	Load the XML file.
			XmlDocument xmlDoc = this.OpenXmlFile( this.Path );
			XmlNamespaceManager nsMgr = this.GetNamespaceManagerForDoc( xmlDoc, this.Namespaces );

			//	Run the XPath Expression against the XML file.
			//	Get the list of matching elements.
			XmlNodeList elements = this.SelectXmlNodesOfType( xmlDoc, this.XPath, XmlNodeType.Element, nsMgr );
			if (elements != null && elements.Count > 0)
			{
				foreach (XmlNode element in elements)
				{
					//	Get the specified attribute.
					XmlNode attrib = element.Attributes[_name];

					//	If the attribute is not found the add it.
					if ( attrib == null )
					{
						attrib = element.Attributes.Append( xmlDoc.CreateAttribute( _name ) );
					}
					
					//	Update the attribute's value.
					attrib.Value = _value;
				}

				//	Save the Xml Doc back to disc.
				this.SaveXmlFile( xmlDoc, this.Path, _force );

				Log.LogMessageFromResources( "XmlFile.SetAttribute.Success", this.FileName, _name, _value, elements.Count, this.XPath );
			}
			else
			{
				if ( _ignoreNoMatchFailure )
					Log.LogWarningFromResources( "XmlFile.NoMatch.Warning", this.FileName, this.XPath );
				else
					throw new TaskException( Log.FormatResourceString( "XmlFile.NoMatch.Exception", this.XPath ) );
			}
		}
		#endregion

		#region public Properties
		/// <summary>
		/// Name of the Attribute to update.  If not specified the element's value will be updated.
		/// </summary>
		/// <value></value>
		[Required]
		public string Name
		{
			get { return _name; }
			set { _name = (value != null ? value : String.Empty); }
		}

		/// <summary>
		/// The new value to assign to the element or attribute.
		/// </summary>
		/// <value>The new value to assign to the element or attribute.</value>
		public string Value
		{
			get { return _value; }
			set { _value = (value != null ? value : String.Empty); }
		}

		/// <summary>
		/// If set to true, no Exception will be thrown if no matches made for the supplied XPath.  Defaults to false.
		/// </summary>
		/// <value>If set to true, no Exception will be thrown if no matches made for the supplied XPath.  Defaults to false.</value>
		public bool IgnoreNoMatchFailure
		{
			get { return _ignoreNoMatchFailure; }
			set { _ignoreNoMatchFailure = value; }
		}

		/// <summary>
		/// If set to true, this will modify the xml file even if it is read-only.  Defaults to false.
		/// </summary>
		/// <value>If set to true, this will modify the xml file even if it is read-only.  Defaults to false.</value>
		public bool Force
		{
			get { return _force; }
			set { _force = value; }
		}
		#endregion
	}
}
